/**
 */

#include "my_thread.h"

#include <ctime>
#include <sstream>
#ifndef _QNX_
#include <sys/syscall.h>
#endif
#include <sys/types.h>
#include "fake_log.h"

namespace MyTest {

MyThread::MyThread(const std::string& in_name, int32_t in_priority, uint32_t in_worker_num,
                   const std::function<void()> in_func, int32_t in_cpusetsize, const cpu_set_t* const in_cpuset)
    : thread_name_(in_name),
      thread_priority_(in_priority),
      thread_worker_num_(in_worker_num),
      func_main_(in_func),
      thread_cpusetsize_(in_cpusetsize),
      thread_cpuset_(in_cpuset),
      is_shutdown_(true) {
}

MyThread::~MyThread() {
  if (!is_shutdown_) {
    thread_shutdown();
  }
}

void MyThread::my_thread_func_(MyThread* thread_ins) {
  if (thread_ins == nullptr) {
    MY_LOG_ERROR("thread_ins is nullptr");
  } else {
#ifndef _QNX_
    if ((thread_ins->thread_cpuset_ != nullptr) && (thread_ins->thread_cpusetsize_ > 0)) {
      const pthread_t tid = pthread_self();
      const auto np_return =
          pthread_setaffinity_np(tid, static_cast<size_t>(thread_ins->thread_cpusetsize_), thread_ins->thread_cpuset_);
      if (np_return != 0) {
        MY_LOG_ERROR("pthread_setaffinity_np failed. return=%d", np_return);
      }
    }
#else
    // qnx ...
#endif
    std::stringstream thread_id_stream;
    thread_id_stream << std::this_thread::get_id();

    MY_LOG_INFO("thread %s starts. pid=%s target_priority=%d", thread_ins->thread_name_.c_str(),
                thread_id_stream.str().c_str(), thread_ins->thread_priority_);
    MyThread::set_self_thread_priority(thread_ins->thread_priority_);
    try {
      thread_ins->func_main_();
    } catch (...) {
      MY_LOG_ERROR("Exception occurred in thread %s", thread_ins->thread_name_.c_str());
    }
  }
}

void MyThread::thread_start() {
  std::lock_guard<std::mutex> lck(thread_mutex_);
  is_shutdown_ = false;
  my_threads_.resize(thread_worker_num_);
  uint32_t thread_idx = 0;
  for (; thread_idx < thread_worker_num_; thread_idx++) {
    my_threads_[thread_idx] = std::make_shared<std::thread>(my_thread_func_, this);
  }
}

void MyThread::thread_shutdown() {
  std::lock_guard<std::mutex> lck(thread_mutex_);
  if (!is_shutdown_) {
    is_shutdown_ = true;
    thread_cond_.notify_all();

    for (const auto& my_t : my_threads_) {
      if (my_t->joinable()) {
        my_t->join();
      }
    }
  }
}

void MyThread::timed_wait(uint32_t useconds) {
  std::unique_lock<std::mutex> lck(thread_mutex_);
  const auto timeout_val = std::chrono::microseconds(useconds);
  do {
    const auto return_val = thread_cond_.wait_for(lck, timeout_val);
    if (return_val == std::cv_status::timeout) {
      MY_LOG_ERROR("thread timed_wait timeout");
    }
  } while (false);
}

void MyThread::set_self_thread_priority(int32_t in_priority) {
  bool go_on = true;
  struct sched_param params;
  struct sched_param current_params;
  int32_t set_policy{0};
  int32_t current_policy{0};
  const pthread_t this_thread = pthread_self();

  int32_t status_ret = pthread_getschedparam(this_thread, &current_policy, &current_params);
  if (status_ret != 0) {
    MY_LOG_ERROR("getschedparam %d", status_ret);
    go_on = false;
  } else {
    MY_LOG_DEBUG("thread current priority is %d (%d), target is %d", current_params.sched_priority, current_policy,
                 in_priority);  // MY_LOG_TRACE
    if (in_priority == 0) {
      go_on = false;
    } else if (in_priority > 0) {
      set_policy = SCHED_FIFO;
      params.sched_priority = current_params.sched_priority + in_priority;
    } else {
      set_policy = SCHED_IDLE;
      params.sched_priority = 0;
    }
  }
  if (go_on) {
    if (params.sched_priority > 99) {
      params.sched_priority = 99;
    }
    if (params.sched_priority < 0) {
      params.sched_priority = 0;
    }
    status_ret = pthread_setschedparam(this_thread, set_policy, &params);
    if (status_ret != 0) {
      MY_LOG_WARN("setschedparam(%d)", params.sched_priority);
      go_on = false;
    }
  }

  if (go_on) {
    status_ret = pthread_getschedparam(this_thread, &current_policy, &current_params);
    if (status_ret != 0) {
      MY_LOG_ERROR("getschedparam 2 %d", status_ret);
    } else {
      if (current_params.sched_priority != params.sched_priority) {
        MY_LOG_ERROR("current priority=%d (%d), target is %d", current_params.sched_priority, current_policy,
                     params.sched_priority);
      } else {
        MY_LOG_INFO("set thread priority to %d (%d)", current_params.sched_priority, current_policy);
      }
    }
  }
}

}  // namespace MyTest
